/**
 *
 */
package com.seteam.collabcrossword;

import com.seteam.collabcrossword.messages.client.*;
import com.seteam.collabcrossword.messages.generic.UpdateLetter;
import com.seteam.collabcrossword.messages.server.Connected;
import com.seteam.collabcrossword.messages.server.SendingSolution;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.net.InetAddress;

/**
 * This class receives the data sent by the client. The data is got through the
 * input stream of the socket.
 * 
 * @author SE Team
 */
public class DataReceiver extends Thread {
	private final Logger logger = LoggerFactory.getLogger(DataReceiver.class);

	// Message Parser containing resources shared between the two clients.
	private MessageParser messageParser = null;
	private SynchronizedQueue<Object> transferQueue = null;
	private InterClientQueue<Object> interClientQueue = null;

	// Only input stream available to the Receiver
	private ObjectInputStream in = null;
	private int shareId;

	// Mutex to signal the thread waiting on the intra-client queue.
	private Object mutex;
	// The object received from the client, may be of type Message.
	private Object theObject;

	// Interface to stop the threads
	private ThreadStopper threadStopper = null;

	/**
	 * 
	 * @param in
	 * @param messageParser
	 * @param transferQueue
	 * @param mutex
	 * @param shareId
	 * @param interClientQueue
	 */
	public DataReceiver(ObjectInputStream in, MessageParser messageParser,
			SynchronizedQueue<Object> transferQueue, Object mutex, int shareId,
			InterClientQueue<Object> interClientQueue) {
		this.messageParser = messageParser;
		this.transferQueue = transferQueue;
		this.interClientQueue = interClientQueue;
		this.in = in;
		this.mutex = mutex;
		theObject = null;
	}

	/**
	 * This method is run when thread start() is called. Here we wait until a
	 * message is available on the input stream of the socket. Once the message
	 * is available, calls a method to process the then read data.
	 */
	public void run() {
		synchronized (this) {
			try {
				logger.info("Waiting for message {}", this.hashCode());
				// A blocked wait, when ever there is no data in the input
				// stream, the execution is blocked here.
				while ((theObject = in.readObject()) != null) {
					process(theObject);
					synchronized (mutex) {
						mutex.notify();
					}
				}
			} catch (final IOException e) {
				e.printStackTrace();
			} catch (final ClassNotFoundException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * Process the Message got from clients. For each different type of Message
	 * received, a reply is sent by the method to the client through the
	 * DataSender.
	 * 
	 * @param theObject
	 *            Object type parameter. The object is serialised, so that it
	 *            can be sent over the network.
	 */
	private void process(final Object theObject) {
		try {
			// First message sent by the client to connect to the server.
			if (theObject instanceof Connect) {
				// Process the connect object, get the Client Ip and set it in
				// the thread.
				logger.info("Connect received {}",
						((Connect) theObject).hashCode());
				InetAddress clientIp = messageParser
						.parseMessage((Connect) theObject);
				// Write the shared Id between the two clients
				Connected connected = new Connected(shareId);
				transferQueue.add(connected);
			}

			// As soon as the client gets a reply that it is connected, it
			// requests the server for a Crossword. The Crossword is sent back
			// to the requesting client only when two clients have joined the
			// game.
			if (theObject instanceof GetBoard) {
				logger.info("Getting board");
				transferQueue.add(messageParser
						.parseMessage((GetBoard) theObject));
			}

			// Client requesting game quit
			if (theObject instanceof Quit) {
				logger.info("Quit received {}", ((Quit) theObject).hashCode());
				// STEP 1: Release all the locks acquired by the client on the
				// word.
				messageParser.parseMessage((Quit) theObject);
				// STEP 2: Write the message to the interClientQueue to inform
				// the other client.
				interClientQueue.add((Quit) theObject);
				logger.info("Quit written into other client queue");
				// STEP 3: Close the thread.(DataReceiver, DataSender and
				// ClientHandler)
				threadStopper.stopThreads();
			}

			// When a word needs to the written,the user must first obtain lock
			// on word that needs to be entered.
			if (theObject instanceof RequestLock) {
				// Request lock for the
				transferQueue.add(messageParser
						.parseMessage((RequestLock) theObject));
			}

			// After the client gets the lock on the word, the update of each
			// letter(by client 1) should reach the other client(client 2) and
			// vice versa. This takes care of letter updates.
			if (theObject instanceof UpdateLetter) {
				logger.info("Update letter received");

				messageParser.parseMessage((UpdateLetter) theObject);

				// sending the updated letter to the other client.
				interClientQueue.add((UpdateLetter) theObject);
			}

			// After the user hits on submit, this sends a Solution to the
			// client so that it can be verified.
			if (theObject instanceof CheckSolution) {
				// STEP 1: Send the message to stop the other client from
				// editing.
				SendingSolution sendingSolution = new SendingSolution();
				interClientQueue.add(sendingSolution);

				// STEP 2: Send the Current Solution in the server to both the
				// clients.
				CrosswordCurrentState currentCrossword = messageParser
						.parseMessage((CheckSolution) theObject);
				interClientQueue.add(currentCrossword);
				transferQueue.add(currentCrossword);

				// STEP 3: Send the Solution to both the clients.
				CrosswordSolution crosswordSolution = messageParser
						.getCrosswordSolution();
				interClientQueue.add(crosswordSolution);
				transferQueue.add(crosswordSolution);
			}

		} catch (final Exception e) {
			logger.debug("Exception: {}", e.getMessage());
		}

	}

	/**
	 * This registers Client Handler as the observer of the ThreadStopper.
	 * 
	 * @param threadStopper
	 *            ThreadStopper Interface.
	 */
	public void registerThreadStopper(ThreadStopper threadStopper) {
		this.threadStopper = threadStopper;
	}
}
